/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * =======================================================================
 *
 * Server startup.
 *
 * =======================================================================
 */

#include "header/server.h"

server_static_t svs; /* persistant server info */
server_t sv;         /* local server */

int
SV_FindIndex ( char *name, int start, int max, qboolean create )
{
	int i;

	if ( !name || !name [ 0 ] )
	{
		return ( 0 );
	}

	for ( i = 1; i < max && sv.configstrings [ start + i ] [ 0 ]; i++ )
	{
		if ( !strcmp( sv.configstrings [ start + i ], name ) )
		{
			return ( i );
		}
	}

	if ( !create )
	{
		return ( 0 );
	}

	if ( i == max )
	{
		Com_Error( ERR_DROP, "*Index: overflow" );
	}

	strncpy( sv.configstrings [ start + i ], name, sizeof ( sv.configstrings [ i ] ) );

	if ( sv.state != ss_loading )
	{   
		/* send the update to everyone */
		MSG_WriteChar( &sv.multicast, svc_configstring );
		MSG_WriteShort( &sv.multicast, start + i );
		MSG_WriteString( &sv.multicast, name );
		SV_Multicast( vec3_origin, MULTICAST_ALL_R );
	}

	return ( i );
}

int
SV_ModelIndex ( char *name )
{
	return ( SV_FindIndex( name, CS_MODELS, MAX_MODELS, true ) );
}

int
SV_SoundIndex ( char *name )
{
	return ( SV_FindIndex( name, CS_SOUNDS, MAX_SOUNDS, true ) );
}

int
SV_ImageIndex ( char *name )
{
	return ( SV_FindIndex( name, CS_IMAGES, MAX_IMAGES, true ) );
}

/*
 * Entity baselines are used to compress the update messages
 * to the clients -- only the fields that differ from the
 * baseline will be transmitted
 */
void
SV_CreateBaseline ( void )
{
	edict_t *svent;
	int entnum;

	for ( entnum = 1; entnum < ge->num_edicts; entnum++ )
	{
		svent = EDICT_NUM( entnum );

		if ( !svent->inuse )
		{
			continue;
		}

		if ( !svent->s.modelindex && !svent->s.sound && !svent->s.effects )
		{
			continue;
		}

		svent->s.number = entnum;

		/* take current state as baseline */
		VectorCopy( svent->s.origin, svent->s.old_origin );
		sv.baselines [ entnum ] = svent->s;
	}
}

void
SV_CheckForSavegame ( void )
{
	char name [ MAX_OSPATH ];
	FILE        *f;
	int i;

	if ( sv_noreload->value )
	{
		return;
	}

	if ( Cvar_VariableValue( "deathmatch" ) )
	{
		return;
	}

	Com_sprintf( name, sizeof ( name ), "%s/save/current/%s.sav", FS_Gamedir(), sv.name );
	f = fopen( name, "rb" );

	if ( !f )
	{
		return; /* no savegame */
	}

	fclose( f );

	SV_ClearWorld();

	/* get configstrings and areaportals */
	SV_ReadLevelFile();

	if ( !sv.loadgame )
	{   
		/* coming back to a level after being in a different
		   level, so run it for ten seconds */

		server_state_t previousState;

		previousState = sv.state;
		sv.state = ss_loading;

		for ( i = 0; i < 100; i++ )
		{
			ge->RunFrame();
		}

		sv.state = previousState;
	}
}

/*
 * Change the server to a new map, taking all connected
 * clients along with it.
 */
void
SV_SpawnServer ( char *server, char *spawnpoint, server_state_t serverstate, qboolean attractloop, qboolean loadgame )
{
	int i;
	unsigned checksum;

	if ( attractloop )
	{
		Cvar_Set( "paused", "0" );
	}

	Com_Printf( "------- server initialization ------\n" );

	Com_DPrintf( "SpawnServer: %s\n", server );

	if ( sv.demofile )
	{
		FS_FCloseFile( (size_t) sv.demofile );
	}

	svs.spawncount++; /* any partially connected client will be restarted */
	sv.state = ss_dead;
	Com_SetServerState( sv.state );

	/* wipe the entire per-level structure */
	memset( &sv, 0, sizeof ( sv ) );
	svs.realtime = 0;
	sv.loadgame = loadgame;
	sv.attractloop = attractloop;

	/* save name for levels that don't set message */
	strcpy( sv.configstrings [ CS_NAME ], server );

	if ( Cvar_VariableValue( "deathmatch" ) )
	{
		sprintf( sv.configstrings [ CS_AIRACCEL ], "%g", sv_airaccelerate->value );
		pm_airaccelerate = sv_airaccelerate->value;
	}
	else
	{
		strcpy( sv.configstrings [ CS_AIRACCEL ], "0" );
		pm_airaccelerate = 0;
	}

	SZ_Init( &sv.multicast, sv.multicast_buf, sizeof ( sv.multicast_buf ) );

	strcpy( sv.name, server );

	/* leave slots at start for clients only */
	for ( i = 0; i < maxclients->value; i++ )
	{
		/* needs to reconnect */
		if ( svs.clients [ i ].state > cs_connected )
		{
			svs.clients [ i ].state = cs_connected;
		}

		svs.clients [ i ].lastframe = -1;
	}

	sv.time = 1000;

	strcpy( sv.name, server );
	strcpy( sv.configstrings [ CS_NAME ], server );

	if ( serverstate != ss_game )
	{
		sv.models [ 1 ] = CM_LoadMap( "", false, &checksum );   /* no real map */
	}
	else
	{
		Com_sprintf( sv.configstrings [ CS_MODELS + 1 ], 
				sizeof ( sv.configstrings [ CS_MODELS + 1 ] ), "maps/%s.bsp", server );
		sv.models [ 1 ] = CM_LoadMap( sv.configstrings [ CS_MODELS + 1 ], false, &checksum );
	}

	Com_sprintf( sv.configstrings [ CS_MAPCHECKSUM ], sizeof ( sv.configstrings [ CS_MAPCHECKSUM ] ),
			"%i", checksum );

	/* clear physics interaction links */
	SV_ClearWorld();

	for ( i = 1; i < CM_NumInlineModels(); i++ )
	{
		Com_sprintf( sv.configstrings [ CS_MODELS + 1 + i ], sizeof ( sv.configstrings [ CS_MODELS + 1 + i ] ),
				"*%i", i );
		sv.models [ i + 1 ] = CM_InlineModel( sv.configstrings [ CS_MODELS + 1 + i ] );
	}

	/* spawn the rest of the entities on the map */
	sv.state = ss_loading;
	Com_SetServerState( sv.state );

	/* load and spawn all other entities */
	ge->SpawnEntities( sv.name, CM_EntityString(), spawnpoint );

	/* run two frames to allow everything to settle */
	ge->RunFrame();
	ge->RunFrame();

	/* verify game didn't clobber important stuff */
	if ( (int) checksum != atoi( sv.configstrings [ CS_MAPCHECKSUM ] ) )
	{
		Com_Error( ERR_DROP, "Game DLL corrupted server configstrings" );
	}

	/* all precaches are complete */
	sv.state = serverstate;
	Com_SetServerState( sv.state );

	/* create a baseline for more efficient communications */
	SV_CreateBaseline();

	/* check for a savegame */
	SV_CheckForSavegame();

	/* set serverinfo variable */
	Cvar_FullSet( "mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET );
	Cvar_SetValue( "windowed_mouse", 1 );

	Com_Printf( "------------------------------------\n\n" );
}

/*
 * A brand new game has been started
 */
void
SV_InitGame ( void )
{
	int i;
	edict_t *ent;
	char idmaster [ 32 ];

	if ( svs.initialized )
	{
		/* cause any connected clients to reconnect */
		SV_Shutdown( "Server restarted\n", true );
	}

#ifndef DEDICATED_ONLY
	else
	{
		/* make sure the client is down */
		CL_Drop();
		SCR_BeginLoadingPlaque();
	}
#endif

	/* get any latched variable changes (maxclients, etc) */
	Cvar_GetLatchedVars();

	svs.initialized = true;

	if ( Cvar_VariableValue( "coop" ) && Cvar_VariableValue( "deathmatch" ) )
	{
		Com_Printf( "Deathmatch and Coop both set, disabling Coop\n" );
		Cvar_FullSet( "coop", "0",  CVAR_SERVERINFO | CVAR_LATCH );
	}

	/* dedicated servers can't be single player and are usually DM 
	   so unless they explicity set coop, force it to deathmatch */
	if ( dedicated->value )
	{
		if ( !Cvar_VariableValue( "coop" ) )
		{
			Cvar_FullSet( "deathmatch", "1",  CVAR_SERVERINFO | CVAR_LATCH );
		}
	}

	/* init clients */
	if ( Cvar_VariableValue( "deathmatch" ) )
	{
		if ( maxclients->value <= 1 )
		{
			Cvar_FullSet( "maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH );
		}
		else if ( maxclients->value > MAX_CLIENTS )
		{
			Cvar_FullSet( "maxclients", va( "%i", MAX_CLIENTS ), CVAR_SERVERINFO | CVAR_LATCH );
		}
	}
	else if ( Cvar_VariableValue( "coop" ) )
	{
		if ( ( maxclients->value <= 1 ) || ( maxclients->value > 4 ) )
		{
			Cvar_FullSet( "maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH );
		}
	}
	else /* non-deathmatch, non-coop is one player */
	{
		Cvar_FullSet( "maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH );
	}

	svs.spawncount = rand();
	svs.clients = Z_Malloc( sizeof ( client_t ) * maxclients->value );
	svs.num_client_entities = maxclients->value * UPDATE_BACKUP * 64;
	svs.client_entities = Z_Malloc( sizeof ( entity_state_t ) * svs.num_client_entities );

	/* init network stuff */
	NET_Config( ( maxclients->value > 1 ) );

	/* heartbeats will always be sent to the id master */
	svs.last_heartbeat = -99999; /* send immediately */
	Com_sprintf( idmaster, sizeof ( idmaster ), "192.246.40.37:%i", PORT_MASTER );
	NET_StringToAdr( idmaster, &master_adr [ 0 ] );

	/* init game */
	SV_InitGameProgs();

	for ( i = 0; i < maxclients->value; i++ )
	{
		ent = EDICT_NUM( i + 1 );
		ent->s.number = i + 1;
		svs.clients [ i ].edict = ent;
		memset( &svs.clients [ i ].lastcmd, 0, sizeof ( svs.clients [ i ].lastcmd ) );
	}
}

/*
 * the full syntax is:
 *
 * map [*]<map>$<startspot>+<nextserver>
 *
 * command from the console or progs.
 * Map can also be a.cin, .pcx, or .dm2 file
 * Nextserver is used to allow a cinematic to play, then proceed to
 * another level:
 *
 *  map tram.cin+jail_e3
 */
void
SV_Map ( qboolean attractloop, char *levelstring, qboolean loadgame )
{
	char level [ MAX_QPATH ];
	char    *ch;
	int l;
	char spawnpoint [ MAX_QPATH ];

	sv.loadgame = loadgame;
	sv.attractloop = attractloop;

	if ( ( sv.state == ss_dead ) && !sv.loadgame )
	{
		SV_InitGame(); /* the game is just starting */
	}

	strcpy( level, levelstring );

	/* if there is a + in the map, set nextserver to the remainder */
	ch = strstr( level, "+" );

	if ( ch )
	{
		*ch = 0;
		Cvar_Set( "nextserver", va( "gamemap \"%s\"", ch + 1 ) );
	}
	else
	{
		Cvar_Set( "nextserver", "" );
	}

	/* hack for end game screen in coop mode */
	if ( Cvar_VariableValue( "coop" ) && !Q_stricmp( level, "victory.pcx" ) )
	{
		Cvar_Set( "nextserver", "gamemap \"*base1\"" );
	}

	/* if there is a $, use the remainder as a spawnpoint */
	ch = strstr( level, "$" );

	if ( ch )
	{
		*ch = 0;
		strcpy( spawnpoint, ch + 1 );
	}
	else
	{
		spawnpoint [ 0 ] = 0;
	}

	/* skip the end-of-unit flag if necessary */
	if ( level [ 0 ] == '*' )
	{
		memmove( level, level + 1, strlen( level ) + 1 );
	}

	l = strlen( level );

	if ( ( l > 4 ) && !strcmp( level + l - 4, ".cin" ) )
	{
#ifndef DEDICATED_ONLY
		SCR_BeginLoadingPlaque(); /* for local system */
#endif
		SV_BroadcastCommand( "changing\n" );
		SV_SpawnServer( level, spawnpoint, ss_cinematic, attractloop, loadgame );
	}
	else if ( ( l > 4 ) && !strcmp( level + l - 4, ".dm2" ) )
	{
#ifndef DEDICATED_ONLY
		SCR_BeginLoadingPlaque(); /* for local system */
#endif
		SV_BroadcastCommand( "changing\n" );
		SV_SpawnServer( level, spawnpoint, ss_demo, attractloop, loadgame );
	}
	else if ( ( l > 4 ) && !strcmp( level + l - 4, ".pcx" ) )
	{
#ifndef DEDICATED_ONLY
		SCR_BeginLoadingPlaque(); /* for local system */
#endif
		SV_BroadcastCommand( "changing\n" );
		SV_SpawnServer( level, spawnpoint, ss_pic, attractloop, loadgame );
	}
	else
	{
#ifndef DEDICATED_ONLY
		SCR_BeginLoadingPlaque(); /* for local system */
#endif
		SV_BroadcastCommand( "changing\n" );
		SV_SendClientMessages();
		SV_SpawnServer( level, spawnpoint, ss_game, attractloop, loadgame );
		Cbuf_CopyToDefer();
	}

	SV_BroadcastCommand( "reconnect\n" );
}

